/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "resize_defrag.h"
#include "hmfs_define.h"
#include "hmfs_common.h"
#include "resize_data.h"
#include "resize_data.h"
#include "hmfs_zoned.h"

namespace OHOS {
namespace Hmfs {
ResizeDefrag::ResizeDefrag(ResizeOperator *optPtr, std::shared_ptr<HmfsSbInfo> sbInfoPtr, std::shared_ptr<HmfsConfigData> configPtr)
 : opt_(optPtr), sbInfo_(sbInfoPtr), config_(configPtr)
{

}

#ifdef HAVE_LINUX_BLKZONED_H
int ResizeDefrag::GetDeviceIdx(std::shared_ptr<HmfsSbInfo> sbi, uint32_t segno)
{
    uint32_t segStartBlkaddr = SM_I(sbi)->mainBlkaddr + segno * DEFAULT_BLOCKS_PER_SEGMENT;
    for (int i = 0; i < config_->nDevices; i++) {
        if (config_->devices[i].startBlkId <= segStartBlkaddr &&
            config_->devices[i].endBlkId > segStartBlkaddr) {
            return i;
        }
    }
    return 0;
}

int ResizeDefrag::GetZoneIdxFromDev(std::shared_ptr<HmfsSbInfo> sbi, uint32_t segno, uint32_t devIdx)
{
    uint32_t segStartBlkaddr = START_BLOCK(sbi, segno);

    return (segStartBlkaddr - config_->devices[devIdx].startBlkId) >>
            HmfsCommon::GetInstance().LogBase2(sbi->segmentsPerSec * sbi->blocksPerSeg);
}

bool ResizeDefrag::IsUsableSegment(std::shared_ptr<HmfsSbInfo> sbi, unsigned int segno)
{
    unsigned int secno = segno / sbi->segmentsPerSec;
    uint32_t segStart = START_BLOCK(sbi, segno);
    uint32_t blocksPerSec = sbi->blocksPerSeg * sbi->segmentsPerSec;
    unsigned int devIdx = GetDeviceIdx(sbi, segno);
    unsigned int zoneIdx = GetZoneIdxFromDev(sbi, segno, devIdx);
    unsigned int secOff = SM_I(sbi)->mainBlkaddr >>
                        HmfsCommon::GetInstance().LogBase2(blocksPerSec);

    if (zoneIdx < config_->devices[devIdx].nrRndZones) {
        return true;
    }
    if (config_->devices[devIdx].zonedModel != HMFS_ZONED_HM) {
        return true;
    }
    return segStart < ((secOff + secno) * blocksPerSec) +
                config_->devices[devIdx].zoneCapBlocks[zoneIdx];
}

#else

bool ResizeDefrag::IsUsableSegment(std::shared_ptr<HmfsSbInfo> sbi, unsigned int segno)
{
    return true;
}

#endif

uint32_t ResizeDefrag::GetFreeSegments()
{
    uint32_t freeSegs = 0;
    for (uint32_t i = 0; i < MAIN_SEGS(sbInfo_); i++) {
        struct SegEntry *se = GetSegEntry(sbInfo_, i);
        if (se->validBlocks == 0x0 && !IS_CUR_SEGNO(sbInfo_, i) && IsUsableSegment(sbInfo_,i)) {
            freeSegs++;
        }
    }
    return freeSegs;
}

unsigned short ResizeDefrag::GetSegVblocks(struct SegEntry *se)
{
    if (!NeedFsyncDataRecord(sbInfo_, config_)) {
        return se->validBlocks;
    } else {
        return se->ckptValidBlocks;
    }
}

unsigned char* ResizeDefrag::GetSegBitmap(struct SegEntry *se)
{
    if (!NeedFsyncDataRecord(sbInfo_, config_)) {
        return se->curValidBitmap;
    } else {
        return se->ckptValidBitmap;
    }
}

unsigned char ResizeDefrag::GetSegType(struct SegEntry *se)
{
    if (!NeedFsyncDataRecord(sbInfo_, config_)) {
        return se->type;
    } else {
        return se->ckptType;
    }
}

void ResizeDefrag::SetSectionType(unsigned int segno, int type)
{
    if (sbInfo_->segmentsPerSec == 1) {
        return;
    }
    for (unsigned int i = 0; i < sbInfo_->segmentsPerSec; i++) {
        struct SegEntry *se = GetSegEntry(sbInfo_, segno + i);
        se->type = type;
    }
}

#ifdef HAVE_LINUX_BLKZONED_H

bool ResizeDefrag::WritePointerAtZoneStart(unsigned int zoneSegno)
{
    struct BlockZone blkz;
    uint32_t block = START_BLOCK(sbInfo_, zoneSegno);
    int logSectorsPerBlock = sbInfo_->logBlockSize - SECTOR_SHIFT;
    int j;

    if (config_->zonedModel != HMFS_ZONED_HM) {
        return true;
    }
    for (j = 0; j < MAX_DEVICES; j++) {
        if (!config_->devices[j].path.c_str())
            break;
        if (config_->devices[j].startBlkId <= block &&
            block <= config_->devices[j].endBlkId)
            break;
    }

    if (j >= MAX_DEVICES) {
        return false;
    }
    uint64_t sector = (block - config_->devices[j].startBlkId) << logSectorsPerBlock;
    int ret = HmfsZoned::GetInstance().HmfsReportZone(j, sector, &blkz);
    if (ret) {
        return false;
    }
    if (BLK_ZONE_TYPE(&blkz) != BLK_ZONE_TYPE_SEQWRITE_REQ)
        return true;

    return BLK_ZONE_SECTOR(&blkz) == BLK_ZONE_WP_SECTOR(&blkz);
}

#else

bool ResizeDefrag::WritePointerAtZoneStart(unsigned int zone_segno)
{
    return true;
}

#endif

int ResizeDefrag::FindNextFreeBlock(uint64_t *to, int left, int wantType, bool newSec)
{
    struct SuperBlockData *sbData = F2FS_RAW_SUPER(sbInfo_);
    struct SegEntry *se;
    uint32_t segno;
    uint32_t offset;
    int notEnough = 0;
    uint64_t endBlkaddr = (GetLeValue(sbData->segmentCountInMain) <<
            GetLeValue(sbData->logBlksPerSeg)) + GetLeValue(sbData->mainBlkId);

    if (*to > 0) {
        *to -= left;
    }
    if (GetFreeSegments() <= SM_I(sbInfo_)->reservedSegments + 1) {
        notEnough = 1;
    }
    while (*to >= SM_I(sbInfo_)->mainBlkaddr && *to < endBlkaddr) {
        unsigned short vblocks;
        unsigned char *bitmap;
        unsigned char type;

        segno = GET_SEGNO(sbInfo_, *to);
        offset = OFFSET_IN_SEG(sbInfo_, *to);

        se = GetSegEntry(sbInfo_, segno);

        vblocks = GetSegVblocks(se);
        bitmap = GetSegBitmap(se);
        type = GetSegType(se);
        
        if (vblocks == sbInfo_->blocksPerSeg) {
            *to = left ? START_BLOCK(sbInfo_, segno) - 1 : START_BLOCK(sbInfo_, segno + 1);
            continue;
        }
        if (!(GetLeValue(sbData->features) & NATIVE_TO_LE32(HMFS_FEATURE_RO)) && IS_CUR_SEGNO(sbInfo_, segno)) {
            *to = left ? START_BLOCK(sbInfo_, segno) - 1 : START_BLOCK(sbInfo_, segno + 1);
            continue;
        }
        if (vblocks == 0 && notEnough) {
            *to = left ? START_BLOCK(sbInfo_, segno) - 1 : START_BLOCK(sbInfo_, segno + 1);
            continue;
        }
        if (vblocks == 0 && !(segno % sbInfo_->segmentsPerSec)) {
            struct SegEntry *se2;
            unsigned int i;

            for (i = 1; i < sbInfo_->segmentsPerSec; i++) {
                se2 = GetSegEntry(sbInfo_, segno + i);
                if (GetSegVblocks(se2)) {
                    break;
                }
            }

            if (i == sbInfo_->segmentsPerSec &&
                WritePointerAtZoneStart(segno)) {
				HMFS_DEBUG("SetSectionType");
                SetSectionType(segno, wantType);
                return 0;
            }
        }

        if (type == wantType && !newSec &&
            !HmfsCommon::GetInstance().HmfsTestBit(offset, (const char *)bitmap)) {
			HMFS_DEBUG("type == wantType");
            return 0;
        }
        *to = left ? *to - 1: *to + 1;
    }
    return -1;
}

void ResizeDefrag::MoveOneCursegInfo(uint64_t from, int left, int i)
{
    struct SuperBlockData *sbData = F2FS_RAW_SUPER(sbInfo_);
    struct CurSegmentInfo *curseg = CURSEG_I(sbInfo_, i);
    struct SummaryBlockData buf;
    int ret;
    uint64_t ssaBlk;
    if ((GetLeValue(sbData->features) & NATIVE_TO_LE32(HMFS_FEATURE_RO))) {
        if (i != CURSEG_HOT_DATA && i != CURSEG_HOT_NODE) {
            return;
        }
        if (i == CURSEG_HOT_DATA) {
            left = 0;
            from = SM_I(sbInfo_)->mainBlkaddr;
        } else {
            left = 1;
            from = EndBlockAddr(sbInfo_);
        }
    } else { //TODO 待调试
        /* update original SSA too */
        ssaBlk = GET_SUM_BLKADDR(sbInfo_, curseg->segNum);
        ret = HmfsIo::GetInstance().DevWriteBlock(curseg->segSumBlk, ssaBlk);
        ASSERT(ret >= 0);
    }
    uint64_t to = from;
    ret = FindNextFreeBlock(&to, left, i, config_->zonedModel == HMFS_ZONED_HM);
    ASSERT(ret == 0);

    // uint32_t oldSegno = curseg->segNum;
    curseg->segNum = GET_SEGNO(sbInfo_, to);
    curseg->nextBlkOffset = OFFSET_IN_SEG(sbInfo_, to);
    curseg->allocType = config_->zonedModel == HMFS_ZONED_HM ? LFS : SSR;

    /* update new segno */
    ssaBlk = GET_SUM_BLKADDR(sbInfo_, curseg->segNum);
    ret = HmfsIo::GetInstance().DevReadBlock(&buf, ssaBlk);
    ASSERT(ret >= 0);

    memcpy(curseg->segSumBlk, &buf, SUM_ENTRIES_SIZE);

    /* update se->types */
    ResetCurSegment(sbInfo_, i);

    // FIX_MSG("Move curseg[%d] %x -> %x after %"PRIx64"\n",
    //     i, oldSegno, curseg->segNum, from);
}

void ResizeDefrag::MoveCursegInfo(uint64_t from, int left)
{
    /* update summary blocks having nullified journal entries */
    for (int i = 0; i < NO_CHECK_TYPE; i++) {
        MoveOneCursegInfo(from, left, i);
    }
}


void ResizeDefrag::ZeroJournalEntries()
{
    for (int i = 0; i < NO_CHECK_TYPE; i++) {
        CURSEG_I(sbInfo_, i)->segSumBlk->journal.nNats = 0;
    }
}

void ResizeDefrag::WriteCursegInfo()
{
    struct CheckPointData *cpData = F2FS_CKPT(sbInfo_);
    for (int i = 0; i < NO_CHECK_TYPE; i++) {
        cpData->allocType[i] = CURSEG_I(sbInfo_, i)->allocType;
        if (i < CURSEG_HOT_NODE) {
            SetLeValue(cpData->curDataSegNo[i], CURSEG_I(sbInfo_, i)->segNum);
            SetLeValue(cpData->curDataBlkOffset[i], CURSEG_I(sbInfo_, i)->nextBlkOffset);
        } else {
            int n = i - CURSEG_HOT_NODE;
            SetLeValue(cpData->curNodeSegNo[n], CURSEG_I(sbInfo_, i)->segNum);
            SetLeValue(cpData->curNodeBlkOffset[n], CURSEG_I(sbInfo_, i)->nextBlkOffset);
        }
    }
}

void ResizeDefrag::RewriteCurrentSitPage(unsigned int segno, struct sitBlockData *sitBlk)
{
    uint32_t blkAddr = CurrentSitAddr(sbInfo_, segno);
    ASSERT(HmfsIo::GetInstance().DevWriteBlock(sitBlk, blkAddr) >= 0);
}

void ResizeDefrag::FlushSitEntries()
{
    struct SitInfo *sitInfo = SIT_I(sbInfo_);
    struct sitBlockData *sitBlk = (sitBlockData *)calloc(BLOCK_SZ, 1);
    ASSERT(sitBlk);
    /* update free segments */
    for (unsigned int segno = 0; segno < MAIN_SEGS(sbInfo_); segno++) {
        struct SegEntry *se = GetSegEntry(sbInfo_, segno);
        if (!se->dirty) {
            continue;
        }
        GetCurrentSitPage(sbInfo_, segno, sitBlk);
        struct sitEntry *sit = &sitBlk->entries[SIT_ENTRY_OFFSET(sitInfo, segno)];
        memcpy(sit->blockBitmap, se->curValidBitmap, SIT_VBLOCK_MAP_SIZE);
        sit->usedBlockCount = NATIVE_TO_LE16((se->type << SIT_VBLOCKS_SHIFT) |
                            se->validBlocks);
        RewriteCurrentSitPage(segno, sitBlk);
    }

    free(sitBlk);
}


void ResizeDefrag::UpdateSumEntry(uint32_t blkAddr, struct SummaryEntry *sum)
{
    struct SuperBlockData *sbData = F2FS_RAW_SUPER(sbInfo_);
    int type;
    if (GetLeValue(sbData->features) & NATIVE_TO_LE32(HMFS_FEATURE_RO)) {
        return;
    }
    uint32_t segno = GET_SEGNO(sbInfo_, blkAddr);
    uint32_t offset = OFFSET_IN_SEG(sbInfo_, blkAddr);
    struct SitInfo *sitInfo = sbInfo_->smInfoTable->sitInfo;
    struct SegEntry *se = &sitInfo->segEntries[segno];

    struct SummaryBlockData *sumBlk = opt_->GetSumBlock(segno, &type);
    memcpy(&sumBlk->entries[offset], sum, sizeof(*sum));
    sumBlk->footer.entryType = IS_NODESEG(se->type) ? SUM_TYPE_NODE : SUM_TYPE_DATA;

    /* write SSA all the time */
    uint32_t getSumblkAddr = (sbInfo_->smInfoTable->ssaBlkaddr) + segno;
    int ret = HmfsIo::GetInstance().DevWriteBlock(sumBlk, getSumblkAddr);
    ASSERT(ret >= 0);
    if (type == SEG_TYPE_NODE || type == SEG_TYPE_DATA ||
                    type == SEG_TYPE_MAX)
        free(sumBlk);
}

int32_t ResizeDefrag::HmfsMigrateBlock(uint64_t from, uint64_t to)
{
    void *raw = calloc(BLOCK_SZ, 1);
    ASSERT(raw != NULL);
    int ret = HmfsIo::GetInstance().DevReadBlock(raw, from);
    ASSERT(ret >= 0);
    ret = HmfsIo::GetInstance().DevWriteBlock(raw, to);
    ASSERT(ret >= 0);
    /* update sit bitmap & valid_blocks && se->type */
    struct SitInfo *sitInfo = sbInfo_->smInfoTable->sitInfo;
    struct SegEntry *se = &sitInfo->segEntries[GET_SEGNO(sbInfo_, from)];
    uint64_t offset = OFFSET_IN_SEG(sbInfo_, from);
    int type = se->type;
    se->validBlocks--;
    HmfsCommon::GetInstance().HmfsClearBit(offset, (char *)se->curValidBitmap);
    se->dirty = 1;

    se = &sitInfo->segEntries[GET_SEGNO(sbInfo_, to)];
    offset = OFFSET_IN_SEG(sbInfo_, to);
    se->type = type;
    se->validBlocks++;
    HmfsCommon::GetInstance().HmfsSetBit(offset, (char *)se->curValidBitmap);
    se->dirty = 1;

    struct SummaryEntry sum;
    /* read/write SSA */
    opt_->GetSumEntry(from, &sum);
    UpdateSumEntry(to, &sum);

    /* if data block, read node and update node block */
    if (IS_DATASEG(type)) {
        opt_->UpdateDataBlkaddr(LE32_TO_NATIVE(sum.nid), LE16_TO_NATIVE(sum.ofsInNode), to);
    } else {
        opt_->UpdateNatBlkaddr(0, LE32_TO_NATIVE(sum.nid), to);
    }
    // HMFS_INFO("Migrate %s block %"PRIx64" -> %"PRIx64"\n", IS_DATASEG(type) ? "data" : "node", from, to);
    free(raw);
    return 0;
}

int32_t ResizeDefrag::HmfsDefragment(uint64_t from, uint64_t len, uint64_t to, int left)
{
    struct SitInfo *sitInfo;
    struct SegEntry *se;
    uint64_t offset;
    /* flush NAT/SIT journal entries */
    opt_->FlushJournalEntries();

    for (uint64_t idx = from; idx < from + len; idx++) {
        uint64_t target = to;
        sitInfo = sbInfo_->smInfoTable->sitInfo;
        se = &sitInfo->segEntries[GET_SEGNO(sbInfo_, idx)];
        offset = OFFSET_IN_SEG(sbInfo_, idx);

        if (!HmfsCommon::GetInstance().HmfsTestBit(offset, (const char *)se->curValidBitmap)) {
            continue;
        }
        if (FindNextFreeBlock(&target, left, se->type, false)) {
            MSG(0, "Not enough space to migrate blocks");
            return -1;
        }
        if (HmfsMigrateBlock(idx, target)) {
            MSG(0, "Found inconsistency: please run FSCK");
            return -1;
        }
    }

    /* update curseg info; can update sit->types */
    MoveCursegInfo(to, left);
    ZeroJournalEntries();
    WriteCursegInfo();
    /* flush dirty sit entries */
    FlushSitEntries();
    opt_->WriteCheckpoint();
    return 0;
}

} // namespace Hmfs
} // namespace OHOS
